/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
  <file> istr_fd.c </file>
  <brief>
    Input manipulating procedures from unix file descriptor or memory ranges.
    This file is part of ULS, Unified Lexical Scheme.
  </brief>
  <author>
    Stanley J. Hong <babel96@gmail.com>, June 2011.
  </author>
*/

#include "common.h"
#include "istr_fd.h"
#include "uls_istream.h"
#include "uls_log.h"

void
istr_fd_reset(istr_fd_t* fd_src, int fd)
{
	fd_src->raw_bufptr = fd_src->raw_buf;
	fd_src->raw_buf_bytes = 0;

	fd_src->fd = fd;
	fd_src->flags = 0;
}

void
istr_fd_init(istr_fd_t* fd_src, int fd)
{
	if (fd < 0) {
		fd_src->raw_buf = NULL;
		fd_src->raw_bufsiz = 0;
	} else {
		str_init(&fd_src->raw_buf, &fd_src->raw_bufsiz, ISTR_FD_BUFSIZ);
	}

	csz_init(&fd_src->ubuf, 2*ISTR_FD_BUFSIZ);
	fd_src->record_boundary_checker = NULL;
	istr_fd_reset(fd_src, fd);
}

void
istr_fd_deinit(istr_fd_t* fd_src)
{
	if (fd_src == NULL) {
		return;
	}

	istr_fd_reset(fd_src, -1);
	csz_deinit(&fd_src->ubuf);

	str_free(&fd_src->raw_buf, &fd_src->raw_bufsiz);
	fd_src->raw_bufptr = NULL;
}

int
istr_fd_readn(istr_fd_t *fd_src, uls_char_t *ptr0, int n)
{
	uls_char_t *ptr = ptr0;
	int rc;

	if (fd_src->flags & ISTR_FD_EOF) {
		if (fd_src->flags & ISTR_FD_ERR)
			err_log("%s: Called again after I/O failed!", __FUNCTION__);
		return 0;
	}

	do {
		if ((rc=uls_read(fd_src->fd, ptr, n)) < 0) {
			if (errno == EINTR)  continue;
			fd_src->flags |= ISTR_FD_EOF | ISTR_FD_ERR;
			break;
		} else if (rc==0) {
			fd_src->flags |= ISTR_FD_EOF;
			break;
		}
		ptr += rc;
	} while ((n -= rc) > 0);

	return (int) (ptr-ptr0);
}

int
refill_fd_src(istr_fd_t *fd_src, int n_bytes)
{
	int rc, n;

	n = fd_src->raw_buf_bytes;
	if (n > 0 && fd_src->raw_buf < fd_src->raw_bufptr) {
		memmove(fd_src->raw_buf, fd_src->raw_bufptr, n);
	}
	fd_src->raw_bufptr = fd_src->raw_buf;

	if (fd_src->flags & ISTR_FD_EOF) {
		return 0;
	}

	rc = fd_src->raw_bufsiz - n;
	if (n_bytes < rc) n_bytes = rc;

	str_append(&fd_src->raw_buf, &fd_src->raw_bufsiz, n, NULL, n_bytes);
	fd_src->raw_bufptr = fd_src->raw_buf;

	if ((rc=istr_fd_readn(fd_src, fd_src->raw_buf + n, n_bytes)) < 0) {
		return -1;
	}

	fd_src->raw_buf_bytes = n + rc;
	return rc;
}

void
init_line_in_fd_src(istr_fd_t *fd_src, const uls_char_t* line, int n_bytes)
{
	if (n_bytes <= 0 || line == NULL) return;

	str_append(&fd_src->raw_buf, &fd_src->raw_bufsiz, 0, line, n_bytes);

	fd_src->raw_bufptr = fd_src->raw_buf;
	fd_src->raw_buf_bytes = n_bytes;
}

static int
istr_quote_proc(istr_fd_t *fd_src, istr_quotetype_t *quotetype,
	uls_literal_t *lit, csz_str_t *ss_dst, int offset1, int* p_lno)
{
	int rc, n_lfs;

	lit->tok_id = quotetype->tok_id;
	lit->offset1 = offset1;
	lit->len1 = csz_length(ss_dst) - offset1;

	// forcing '\0' before literal-string.
	csz_add_eos(ss_dst);

	lit->offset2 = csz_length(ss_dst);
	lit->text = NULL;

	n_lfs = *p_lno;
	if ((rc=quotetype->literal_analyzer(fd_src, quotetype->end_mark, ss_dst, lit, p_lno)) < 0) {
		return -1;
	}

	lit->n_lfs = *p_lno -  n_lfs;
	if (lit->text == NULL) {
		lit->len_text = csz_length(ss_dst) - lit->offset2;
	}

	// put '\0' at the end of literal string
	csz_add_eos(ss_dst);

	return 1;
}

#define istr_is_utf8_mb(lptr, lptr_end, len) \
	(len=1, ((*lptr & 0x80) != 0 && (len=uls_dec_utf8(lptr, lptr_end, NULL)) > 1))

static int
istr_space_proc(uls_char_t* ch_ctx,
	uls_char_t *lptr1, uls_char_t **p_lptr, uls_char_t *lptr_end, csz_str_t *ss_dst, int *p_lno)
{
	uls_char_t *lptr = *p_lptr;
	int  rc, n_spaces, n_lfs, n_bytes=0;
	uls_char_t  ch;

	if ((rc = (int) (lptr-lptr1)) > 0)
		csz_append(ss_dst, lptr1, rc);
	n_spaces = n_lfs = 0;

	for (ch = *lptr; ; ) {
		if (ch == '\n') { // treat as special cases
			if (n_spaces > 0) {
				csz_putc(ss_dst, ' ');
				n_spaces = 0;
			}
			csz_putc(ss_dst, '\n');
			++n_lfs;
		} else {
			if (++n_spaces >= INT_MAX >> 1) {
				csz_putc(ss_dst, ' ');
				n_spaces = 0;
			}
		}

		if (++lptr == lptr_end) {
			n_bytes = 0;
			break;
		}

		ch = *lptr;
		if (istr_is_utf8_mb(lptr, lptr_end, rc) || ch_ctx[ch] != 0) {
			n_bytes = rc;
			break;
		}
	}

	if (n_spaces > 0) csz_putc(ss_dst, ' ');
	*p_lptr = lptr;
	*p_lno += n_lfs;

	return n_bytes;
}

static istr_commtype_t*
is_commtype_start(istr_commtype_t *commtypes, int n_commtypes, uls_char_t *ptr, int len)
{
	istr_commtype_t *e;
	uls_char_t *str;
	int i;

	for (i=0; i<n_commtypes; i++) {
		e = commtypes + i;

		// e->len_start_mark > 0
		if (len < e->len_start_mark) continue;

		str = e->start_mark;
		if (ptr[0] == str[0]) {
			if (e->len_start_mark == 1 || !uls_strncmp(ptr+1,str+1, e->len_start_mark-1)) {
				return e;
			}
		}
	}

	return NULL;
}

static int
istr_fd_skip_comment(istr_commtype_t *cmt, istr_fd_t *fd_src, int* p_lfs)
{
	uls_char_t  *lptr, *lptr_end, *marker;
	int   n_lfs = 0, len_marker;
	int   stat = 1; // --> 'stat' success

	lptr = fd_src->raw_bufptr;
	lptr_end = lptr + fd_src->raw_buf_bytes;

	marker = cmt->end_mark;
	len_marker = cmt->len_end_mark;

	for ( ; ; lptr++) {
		if (lptr_end < lptr + len_marker) {
			fd_src->raw_bufptr = lptr;
			fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

			if (refill_fd_src(fd_src, len_marker) < 0) {
				err_log("%s: I/O error", __FUNCTION__);
				lptr = lptr_end = fd_src->raw_bufptr = fd_src->raw_buf;
				fd_src->raw_buf_bytes = 0;
				fd_src->flags |= ISTR_FD_EOF | ISTR_FD_ERR;
				stat = -1; // error
				break;
			} else if (fd_src->raw_buf_bytes < len_marker) {
				lptr = lptr_end = fd_src->raw_bufptr = fd_src->raw_buf;
				fd_src->raw_buf_bytes = 0;
				fd_src->flags |= ISTR_FD_EOF;
				stat = 0; // comment unterminated!
				break;
			}

			lptr = fd_src->raw_bufptr;
			lptr_end = lptr + fd_src->raw_buf_bytes;
		}

		// (lptr_end - lptr) >= len_marker > 0
		if (lptr[0] == marker[0]) {
			if (len_marker == 1 || !uls_strncmp(lptr+1,marker+1, len_marker-1)) {
				lptr += len_marker;
				break;
			}
		}

		if (*lptr == '\n') ++n_lfs;
	}

	fd_src->raw_bufptr = lptr;
	fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

	*p_lfs = n_lfs;
	return stat;
}

static int
istr_mem_skip_comment(istr_commtype_t *cmt, istr_fd_t *fd_src, int *p_lfs)
{
	uls_char_t  *lptr, *lptr_end, *marker;
	int n_lfs = 0, len_marker;
	int   stat = 1; // --> 'stat' success

	lptr = fd_src->raw_bufptr;
	lptr_end = lptr + fd_src->raw_buf_bytes;

	marker = cmt->end_mark;
	len_marker = cmt->len_end_mark;

	for ( ; ; lptr++) {
		if (lptr_end < lptr + len_marker) {
			lptr = lptr_end;
			fd_src->flags |= ISTR_FD_EOF;
			stat = 0; // comment unterminated!
			break;
		}

		// (lptr_end - lptr) >= len_marker > 0
		if (lptr[0] == marker[0]) {
			if (len_marker == 1 || !uls_strncmp(lptr+1,marker+1, len_marker-1)) {
				lptr += len_marker;
				break;
			}
		}

		if (*lptr == '\n') ++n_lfs;
	}

	fd_src->raw_bufptr = lptr;
	fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

	*p_lfs = n_lfs;
	return n_lfs;
}

int
istr_fd_filler(uls_istream_t* istr, uls_char_t** p_line, uls_literal_t* lits, int* ptr_n_lits)
{
	istr_fd_t *fd_src = &istr->fd_context.fd_source;
	uls_char_t* ch_ctx = istr->ch_context;
	int lno=istr->fd_context.lineno;

	csz_str_t *ss_dst = &fd_src->ubuf;
	uls_literal_t   *lit;
	uls_char_t  *lptr1, *lptr, *lptr_end;
	int   n=0, offset1, rc, len_mch, len_surplus;
	uls_char_t  ch, ch_grp;

	istr_commtype_t *commtype;
	istr_quotetype_t *quotetype;
	int i, n_lfs;

	if (istr->n_commtypes > 0) {
		len_surplus = istr->commtypes[0].len_start_mark;
	} else {
		len_surplus = 0;
	}

	if (istr->n_quotetypes > 0) {	
		if (len_surplus < istr->quotetypes[0].len_start_mark)
			len_surplus = istr->quotetypes[0].len_start_mark;
	} else if (len_surplus == 0) {
		len_surplus = 1;
	}

	if (len_surplus < UTF8_CH_MAXLEN) len_surplus = UTF8_CH_MAXLEN;

	offset1 = csz_length(ss_dst);
	lptr1 = lptr = fd_src->raw_bufptr;
	lptr_end = lptr + fd_src->raw_buf_bytes;

	for ( ; ; ) {
		if (lptr_end < lptr + len_surplus) {
			if ((rc = (int) (lptr-lptr1)) > 0) csz_append(ss_dst, lptr1, rc);

			fd_src->raw_bufptr = lptr;
			fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

			if (refill_fd_src(fd_src, len_surplus) < 0) {
				err_log("%s: I/O error", __FUNCTION__);
				return -1;
			}

			lptr1 = lptr = fd_src->raw_bufptr;
			lptr_end = lptr + fd_src->raw_buf_bytes;
			if (fd_src->raw_buf_bytes == 0) {
				break;
			}
		}

		ch_grp = ch_ctx[ch=*lptr];

		if (!istr_is_utf8_mb(lptr, lptr_end, len_mch)) {
			if (ch_grp == 0) {
				istr_space_proc(ch_ctx, lptr1, &lptr, lptr_end, ss_dst, &lno);
				if (csz_length(ss_dst) > ISTR_FD_BUFSIZ) {
					fd_src->raw_bufptr = lptr;
					fd_src->raw_buf_bytes = (int) (lptr_end - lptr);
					break;
				}
				lptr1 = lptr;
				continue;
			}
		}

		if (ch_grp & ULS_CH_COMM) {
			if ((commtype=is_commtype_start(
				istr->commtypes, istr->n_commtypes, lptr, (int) (lptr_end - lptr))) != NULL) {
				lptr += commtype->len_start_mark;
				fd_src->raw_bufptr = lptr;
				fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

				rc = istr_fd_skip_comment(commtype, fd_src, &n_lfs);

				lptr1 = lptr = fd_src->raw_bufptr;
				lptr_end = lptr + fd_src->raw_buf_bytes;

				if (rc <= 0) {
					istr->fd_context.last_end_mark = commtype; // marking!
					break;
				}

				lno += n_lfs + commtype->n_lfs;
				for (i=0; i < n_lfs + commtype->n_lfs; i++)
					csz_putc(ss_dst, '\n');

				continue;
			}
		}

		if ((ch_grp & ULS_CH_QUOTE) && (quotetype=istr_find_quotetype(
			istr, lptr, (int) (lptr_end - lptr))) != NULL) {
			if ((rc = (int) (lptr-lptr1)) > 0) csz_append(ss_dst, lptr1, rc);

			if (n >= ULS_ISTR_LITERALS_MAX) {
				fd_src->raw_bufptr = lptr;
				fd_src->raw_buf_bytes = (int) (lptr_end - lptr);
				break;
			}

			lptr += quotetype->len_start_mark;
			fd_src->raw_bufptr = lptr;
			fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

			if ((rc=istr_quote_proc(fd_src, quotetype, lits+n, ss_dst, offset1, &lno)) < 0) {
				err_log("[%s:%d] unterminated quote-string", istr->fd_context.tag, lno);
				return -1;
			}
			++n;

			offset1 = csz_length(ss_dst);
			lptr1 = lptr = fd_src->raw_bufptr;
			lptr_end = lptr + fd_src->raw_buf_bytes;

		} else {
			if (len_mch == 1 && ch == '\n') ++lno;
			lptr += len_mch;
		}
	}

	lit = lits + n;

	lit->offset1 = offset1;
	lit->len1 = csz_length(ss_dst) - offset1;

	// forcing '\0' before literal-string.
	csz_add_eos(ss_dst);

	lit->text = NULL;
	lit->len_text = -1;
	lit->offset2 = -1; 

	*p_line = fd_src->ubuf.pool + lits[0].offset1;
	 *ptr_n_lits = n;

	return lits[0].len1;
}

int
istr_mem_filler(uls_istream_t* istr, uls_char_t** p_line, uls_literal_t* lits, int* ptr_n_lits)
{
	istr_fd_t *fd_src = &istr->fd_context.fd_source;
	uls_char_t* ch_ctx = istr->ch_context;
	int  lno=istr->fd_context.lineno;

	csz_str_t *ss_dst = &fd_src->ubuf;
	uls_literal_t   *lit;
	uls_char_t  *lptr1, *lptr, *lptr_end;
	int   n=0, offset1, rc, len_mch;
	uls_char_t  ch, ch_grp;

	istr_commtype_t *commtype;
	istr_quotetype_t *quotetype;
	int i, n_lfs;

	offset1 = csz_length(ss_dst);
	lptr1 = lptr = fd_src->raw_bufptr;
	lptr_end = lptr + fd_src->raw_buf_bytes;

	for ( ; ; ) {
		if (lptr == lptr_end) {
			if ((rc = (int) (lptr - lptr1)) > 0) csz_append(ss_dst, lptr1, rc);
			fd_src->raw_bufptr = fd_src->raw_buf;
			fd_src->raw_buf_bytes = 0;
			fd_src->flags |= ISTR_FD_EOF;
			break;
		}

		ch_grp = ch_ctx[ch=*lptr];

		if (!istr_is_utf8_mb(lptr, lptr_end, len_mch)) {
			if (ch_grp == 0) {
				istr_space_proc(ch_ctx, lptr1, &lptr, lptr_end, ss_dst, &lno);
				if (csz_length(ss_dst) > ISTR_FD_BUFSIZ) {
					fd_src->raw_bufptr = lptr;
					fd_src->raw_buf_bytes = (int) (lptr_end - lptr);
					break;
				}
	
				lptr1 = lptr;
				continue;
			}
		}

		if (ch_grp & ULS_CH_COMM) {
			if ((commtype=is_commtype_start(
				istr->commtypes, istr->n_commtypes, lptr, (int) (lptr_end - lptr))) != NULL) {
				lptr += commtype->len_start_mark;
				fd_src->raw_bufptr = lptr;
				fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

				rc = istr_mem_skip_comment(commtype, fd_src, &n_lfs);

				lptr1 = lptr = fd_src->raw_bufptr;
				lptr_end = lptr + fd_src->raw_buf_bytes;

				if (rc <= 0) {
					istr->fd_context.last_end_mark = commtype; // marking!
					break;
				}

				lno += n_lfs + commtype->n_lfs;
				for (i=0; i < n_lfs + commtype->n_lfs; i++)
					csz_putc(ss_dst, '\n');

				continue;
			}
		}

		if ((ch_grp & ULS_CH_QUOTE) && (quotetype=istr_find_quotetype(
			istr, lptr, (int) (lptr_end - lptr))) != NULL) {
			if ((rc = (int) (lptr-lptr1)) > 0) csz_append(ss_dst, lptr1, rc);

			if (n >= ULS_ISTR_LITERALS_MAX) {
				fd_src->raw_bufptr = lptr;
				fd_src->raw_buf_bytes = (int) (lptr_end - lptr);
				break;
			}

			lptr += quotetype->len_start_mark;
			fd_src->raw_bufptr = lptr;
			fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

			if ((rc=istr_quote_proc(fd_src, quotetype, lits+n, ss_dst, offset1, &lno)) < 0) {
				err_log("[%s:%d] unterminated quote-string", istr->fd_context.tag, lno);
				return -1;
			}
			++n;

			offset1 = csz_length(ss_dst);
			lptr1 = lptr = fd_src->raw_bufptr;
			lptr_end = lptr + fd_src->raw_buf_bytes;

		} else {
			if (len_mch == 1 && ch == '\n') ++lno;
			lptr += len_mch;
		}
	}

	lit = lits + n;

	lit->offset1 = offset1;
	lit->len1 = csz_length(ss_dst) - offset1;

	// forcing '\0' before literal-string.
	csz_add_eos(ss_dst);

	lit->text = NULL;
	lit->len_text = -1;
	lit->offset2 = -1; 

	*p_line = fd_src->ubuf.pool + lits[0].offset1;
	 *ptr_n_lits = n;

	return lits[0].len1;
}

int
check_rec_boundary_bin_le(uls_char_t* buf, int n)
{
	int    txtlen, reclen;
	
	if (n < ULS_BIN_RECHDR_SZ) {
		return 0;
	}

	txtlen = ((uls_int32 *) buf)[1];
	reclen = ULS_BIN_RECHDR_SZ + uls_roundup(txtlen+1, 4); // aligned by 4
	if (reclen > n) {
		return -reclen;
	}

	// reclen > ULS_BIN_RECHDR_SZ > 0
	return reclen;
}

int
check_rec_boundary_bin_be(uls_char_t* buf, int n)
{
	int    txtlen, reclen;

	if (n < ULS_BIN_RECHDR_SZ) {
		return 0;
	}

	txtlen = ((uls_int32 *) buf)[1];
	uls_reverse_array((uls_byte_t *) &txtlen, sizeof(uls_int32));

	if (txtlen < 0)
		err_panic("%s: corrupt stream!", __FUNCTION__);

	reclen = ULS_BIN_RECHDR_SZ + uls_roundup(txtlen+1, 4); // aligned by 4

	if (reclen > n) {
		return -reclen;
	}

	// Do lazy byte conversion
	uls_reverse_array((uls_byte_t *) buf, sizeof(uls_int32));
	uls_reverse_array((uls_byte_t *) buf + sizeof(uls_int32), sizeof(uls_int32));

	// reclen > ULS_BIN_RECHDR_SZ > 0
	return reclen;
}

int
istr_binfd_filler(uls_istream_t* istr,
	uls_char_t** p_line, uls_literal_t* lits, int* ptr_n_lits)
{
	istr_fd_t *fd_src = &istr->fd_context.fd_source;
	int    i0, n, m2, rc, n_bytes, reclen, n_recs;

	*ptr_n_lits = 0;

	if ((n_bytes = fd_src->raw_buf_bytes) > 0 && fd_src->raw_buf < fd_src->raw_bufptr) {
		memmove(fd_src->raw_buf, fd_src->raw_bufptr, n_bytes);
	}

	rc = fd_src->raw_bufsiz - n_bytes;

	if (rc <= 0) {
		n = 0;
	} else if ((n=istr_fd_readn(fd_src, fd_src->raw_buf + n_bytes, rc)) <= 0) {
		if (n==0) {
			if (n_bytes > 0) return -1;
			else return 0;
		} else {
			return -1;
		}
	}

	n_bytes += n;

	i0 = 0;
	n = n_bytes;

	for (n_recs=0; ; n_recs++) {
		if ((reclen=fd_src->record_boundary_checker(fd_src->raw_buf + i0, n)) > 0) {
			i0 += reclen;
			n -= reclen;

		} else if (reclen < 0) {
			reclen = -reclen;
			m2 = reclen - n;

			str_append(&fd_src->raw_buf, &fd_src->raw_bufsiz, n_bytes, NULL, m2);
			if (istr_fd_readn(fd_src, fd_src->raw_buf + n_bytes, m2) < m2) {
				err_log("%s: file error", __FUNCTION__);
				fd_src->flags |= ISTR_FD_ERR;
				return -1;
			}

			n = reclen;

		} else { // reclen == 0
			break;
		}
	}

	*p_line = fd_src->raw_buf;

	fd_src->raw_buf_bytes = n;
	fd_src->raw_bufptr = fd_src->raw_buf + i0;

	return i0;
}

uls_gettok_t
find_fd_filler(int fd_type, int has_comm,
	istr_filler_t* p_fillproc, istr_fd_checker_t* p_subproc)
{
	uls_gettok_t proc;

	if (fd_type == ULS_STREAM_RAW) {
		proc = uls_gettok_raw;
		*p_fillproc = istr_fd_filler;
		*p_subproc = NULL;
#ifdef ULS_BIG_ENDIAN
	} else if (fd_type == ULS_STREAM_BIN_LE) {
		proc = uls_gettok_bin;
		*p_fillproc = istr_binfd_filler;
		*p_subproc = check_rec_boundary_bin_be;

	} else if (fd_type == ULS_STREAM_BIN_BE) {
		proc = uls_gettok_bin;
		*p_fillproc = istr_binfd_filler;
		*p_subproc = check_rec_boundary_bin_le;
#else
	} else if (fd_type == ULS_STREAM_BIN_LE) {
		proc = uls_gettok_bin;
		*p_fillproc = istr_binfd_filler;
		*p_subproc = check_rec_boundary_bin_le;

	} else if (fd_type == ULS_STREAM_BIN_BE) {
		proc = uls_gettok_bin;
		*p_fillproc = istr_binfd_filler;
		*p_subproc = check_rec_boundary_bin_be;
#endif
	} else {
		proc = NULL;
	}

	return proc;
}

static int
__verbatim_litch_analyzer(
	int *p_esc, uls_char_t *lptr, uls_char_t* marker, int len_marker)
{
	int escape = *p_esc;
	uls_char_t ch;

	if (escape) {
		escape = 0;
	} else {
		if ((ch=*lptr)=='\\') {
			escape = 1;
		} else {
			if (uls_strncmp(lptr, marker, len_marker) == 0) {
				return 0;
			}
		}
	}

	*p_esc = escape;

	return 1;
}

static int
__default_litch_analyzer(int *p_esc, uls_char_t **p_lptr,
	uls_char_t* marker, int len_marker, csz_str_t *ss_dst, int* p_lno)
{
	int escape = *p_esc, rc, j;
	uls_char_t *lptr = *p_lptr;
	uls_char_t buff[4];
	uls_uch_t  uch;

	if (escape) {
		uch = uls_get_escape_char((char **) &lptr);

		rc = uls_encode_utf8(uch, buff);
		for (j=0; j<rc; j++) {
			csz_putc(ss_dst, buff[j]);
		}

		escape = 0;

	} else {
		if (uls_strncmp(lptr, marker, len_marker) == 0) {
			return 0;
		}

		if ((rc=uls_decode_utf8(lptr, &uch)) <= 0) {
			uch = *lptr;
			rc = 1;
		}

		if (uch == '\n') ++*p_lno;

		if (uch == '\\') {
			escape = 1;
		} else {
			for (j=0; j<rc; j++) csz_putc(ss_dst, lptr[j]);
		}

		lptr += rc;
	}

	*p_esc = escape;
	*p_lptr = lptr;

	return rc;
}

int
__generic_lit_analyzer(int verbatim,
	istr_fd_t *fd_src, uls_char_t* quote_str, csz_str_t *ss_dst, uls_literal_t* lit, int* p_lno)
{
	int  stat = 0;
	uls_char_t *lptr, *lptr_end;
	int   len, len_marker, escape=0;
	uls_char_t  ch;

	lptr = fd_src->raw_bufptr;
	lptr_end = lptr + fd_src->raw_buf_bytes;

	len_marker = uls_strlen(quote_str);
	len = csz_length(ss_dst);

	/* writing escape chars to ss_dst */
	for ( ; ; ) {
		if (lptr_end < lptr + len_marker) {
			fd_src->raw_bufptr = lptr;
			fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

			if (refill_fd_src(fd_src, len_marker) < 0) {
				err_log("%s: I/O error", __FUNCTION__);
				return -1;
			}

			stat = 1;

			lptr = fd_src->raw_bufptr;
			lptr_end = lptr + fd_src->raw_buf_bytes;

			if (fd_src->raw_buf_bytes < len_marker) {
				err_log("Unterminated literal-string");
				return -1;
			}
		}

		ch = 0;

		if (verbatim) {
			if (__verbatim_litch_analyzer(&escape, lptr, quote_str, len_marker) == 0) {
				break;
			}

			if ((ch=*lptr) == '\n') ++*p_lno;
			csz_putc(ss_dst, ch);
			++lptr;

		} else {
			if (__default_litch_analyzer(&escape, &lptr, quote_str, len_marker, ss_dst, p_lno) == 0) {
				break;
			}
		}
	}

	lptr += len_marker;  // the next to the matching right-quote.

	fd_src->raw_bufptr = lptr;
	fd_src->raw_buf_bytes = (int) (lptr_end - lptr);

	lit->len_text = csz_length(ss_dst) - len;
	return stat;
}
